import random
import time
import math
import copy


class Suit(object):
    HEARTS = "hearts"
    CLUBS = "clubs"
    DIAMONDS = "diamonds"
    SPADES = "spades"


class Card(object):
    def __init__(self, suit, value):
        """
        @type suit: str
        @type value: int
        """
        if suit == 1:
            self._suit = Suit.HEARTS
        elif suit == 2:
            self._suit = Suit.CLUBS
        elif suit == 3:
            self._suit = Suit.DIAMONDS
        elif suit == 4:
            self._suit = Suit.SPADES

        self._value = value

    def __str__(self):
        """
        @return: string representation of the card
        """
        return `self._value` + " of " + str(self._suit)

    def __repr__(self):
        """
        @return: string representation of the card
        """
        return `self._value` + str(self._suit[0])


class Deck(list):
    def __init__(self, shuffled=True):
        """
        @type shuffled: bool
        @param shuffled: set it to true if you want a shuffled deck
        """
        for i in range(1, 5):
            for j in range(1, 14):
                card = Card(i, j)
                self.append(card)

        if shuffled:
            random.shuffle(self)


    def shuffle(self):
        """
        Shuffle the deck
        @rtype : None
        """
        random.shuffle(self)


    def discard(self):
        """
        Discards the first card on top of the deck
        @rtype: Card
        """
        return self.pop()


class Evaluator(object):
    def __init__(self):
        """
        @type cards = list[Card]
        """
        self._strategies = [RoyalFlush(),
                            StraightFlush(),
                            Poker(),
                            Flush(),
                            FullHouse(),
                            Straight(),
                            ThreeOfAKind(),
                            TwoPair(),
                            Pair()]

    def set_cards(self, cards):
        self._cards_by_suit = {Suit.CLUBS: [],
                               Suit.DIAMONDS: [],
                               Suit.HEARTS: [],
                               Suit.SPADES: []
        }
        for c in cards:
            self._cards_by_suit[c._suit].append(c._value)

        self._cards_by_suit[Suit.CLUBS].sort()
        self._cards_by_suit[Suit.DIAMONDS].sort()
        self._cards_by_suit[Suit.HEARTS].sort()
        self._cards_by_suit[Suit.SPADES].sort()

        self._cards = cards

        self._cards.sort(key=lambda card: card._value)

        self._cards_by_value = []
        for c in self._cards:
            self._cards_by_value.append(c._value)

    def evaluate(self):
        i = 10
        for s in self._strategies:
            success = s.evaluate(self._cards_by_value, self._cards_by_suit, self._cards)
            i -= 1
            if success:
                #print s.__class__.__name__
                return i
        return 0


class Strategy:
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        pass


class RoyalFlush(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        clubs = False
        diamonds = False
        hearts = False
        spades = False
        if cards_by_suit[Suit.CLUBS]:
            clubs = self._evaluate(cards_by_suit[Suit.CLUBS])
        if cards_by_suit[Suit.DIAMONDS]:
            diamonds = self._evaluate(cards_by_suit[Suit.DIAMONDS])
        if cards_by_suit[Suit.HEARTS]:
            hearts = self._evaluate(cards_by_suit[Suit.HEARTS])
        if cards_by_suit[Suit.SPADES]:
            spades = self._evaluate(cards_by_suit[Suit.SPADES])
        return clubs or diamonds or hearts or spades


    def _evaluate(self, cards, i=13):
        if not cards:
            return False

        if i == 1:
            if i in cards:
                return True
            else:
                return False

        if i <= 10:
            return self._evaluate(cards[0:-1], 1)
        if i in cards:
            return self._evaluate(cards[0:-1], i - 1)
        else:
            return False


class StraightFlush(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        clubs = False
        diamonds = False
        hearts = False
        spades = False
        if len(cards_by_suit[Suit.CLUBS]) >= 5:
            clubs = self._evaluate(cards_by_suit[Suit.CLUBS])
        if len(cards_by_suit[Suit.DIAMONDS]) >= 5:
            diamonds = self._evaluate(cards_by_suit[Suit.DIAMONDS])
        if len(cards_by_suit[Suit.HEARTS]) >= 5:
            hearts = self._evaluate(cards_by_suit[Suit.HEARTS])
        if len(cards_by_suit[Suit.SPADES]) >= 5:
            spades = self._evaluate(cards_by_suit[Suit.SPADES])
        return clubs or diamonds or hearts or spades


    def _evaluate(self, cards, j=5, value=-1):
        if j <= 0:
            return True

        if not cards:
            return False

        if value == -1:
            value = cards[0]
        i = cards[0]

        if i == value:
            return self._evaluate(cards[1:], j - 1, value + 1)
        else:
            return self._evaluate(cards[1:], 5, -1)


class Poker(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards):
        if len(cards) < 4:
            return False

        val = cards[0]
        count = cards.count(val)

        if count >= 4:
            return True
        else:
            return self._evaluate(cards[count:])


class FullHouse(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards, two=False, three=False):
        if two and three:
            return True

        if not cards:
            return False

        val = cards[0]
        count = cards.count(val)

        if count >= 3:
            return self._evaluate(cards[count:], two, True)
        elif count >= 2:
            return self._evaluate(cards[count:], True, three)
        else:
            return self._evaluate(cards[count:], two, three)


class Flush(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        if len(cards_by_suit[Suit.DIAMONDS]) >= 5:
            return True
        elif len(cards_by_suit[Suit.HEARTS]) >= 5:
            return True
        elif len(cards_by_suit[Suit.SPADES]) >= 5:
            return True
        elif len(cards_by_suit[Suit.CLUBS]) >= 5:
            return True
        else:
            return False


class Straight(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards, count=5, val=-1):
        if count == 0:
            return True
        if not cards:
            return False

        if val == -1:
            val = cards[0]

        i = cards[0]

        if i == val:
            return self._evaluate(cards[1:], count - 1, val + 1)
        else:
            return self._evaluate(cards[1:], 5, -1)


class ThreeOfAKind(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards):
        if not cards:
            return False

        val = cards[0]

        count = cards.count(val)

        if count >= 3:
            return True
        else:
            return self._evaluate(cards[count:])


class TwoPair(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards, two=False):
        if len(cards) < 2:
            return False

        val = cards[0]
        count = cards.count(val)

        if count >= 2:
            if two:
                return True
            else:
                return self._evaluate(cards[count:], True)
        else:
            return self._evaluate(cards[1:], two)


class Pair(Strategy):
    def evaluate(self, cards_by_value, cards_by_suit, cards):
        return self._evaluate(cards_by_value)

    def _evaluate(self, cards):
        if not cards:
            return False

        val = cards[0]
        count = cards.count(val)

        if count >= 2:
            return True
        else:
            return self._evaluate(cards[count:])


def cards_royal_flush():
    cards = []
    cards.append(Card(1, 13))
    cards.append(Card(1, 1))
    cards.append(Card(1, 12))
    cards.append(Card(1, 10))
    cards.append(Card(1, 11))
    return cards


def cards_straight_flush():
    cards = []
    cards.append(Card(1, 4))
    cards.append(Card(1, 5))
    cards.append(Card(1, 7))
    cards.append(Card(1, 8))
    cards.append(Card(1, 6))
    return cards


def cards_four_of_a_kind():
    cards = []
    cards.append(Card(1, 5))
    cards.append(Card(2, 5))
    cards.append(Card(3, 5))
    cards.append(Card(4, 5))
    cards.append(Card(1, 1))
    return cards


def cards_full_house():
    cards = []
    cards.append(Card(1, 5))
    cards.append(Card(2, 5))
    cards.append(Card(3, 5))
    cards.append(Card(4, 2))
    cards.append(Card(1, 2))
    return cards


def cards_flush():
    cards = []
    cards.append(Card(1, 10))
    cards.append(Card(1, 4))
    cards.append(Card(1, 11))
    cards.append(Card(1, 2))
    cards.append(Card(1, 7))
    return cards


def cards_straight():
    cards = []
    cards.append(Card(1, 2))
    cards.append(Card(3, 4))
    cards.append(Card(4, 5))
    cards.append(Card(3, 3))
    cards.append(Card(2, 6))
    return cards


def cards_three_of_a_kind():
    cards = []
    cards.append(Card(1, 12))
    cards.append(Card(1, 3))
    cards.append(Card(4, 12))
    cards.append(Card(3, 7))
    cards.append(Card(2, 12))
    return cards


def cards_two_pair():
    cards = []
    cards.append(Card(1, 5))
    cards.append(Card(2, 5))
    cards.append(Card(3, 3))
    cards.append(Card(4, 3))
    cards.append(Card(2, 12))
    return cards


def cards_pair():
    cards = []
    cards.append(Card(1, 5))
    cards.append(Card(2, 5))
    cards.append(Card(3, 4))
    cards.append(Card(4, 3))
    cards.append(Card(2, 12))
    return cards


if __name__ == "__main__":
    cards = []
    success_count = [0] * 10
    counter = 0
    test_count = 1000000
    percentage = [50.11,
                  42.26,
                  4.75,
                  2.11,
                  0.39,
                  0.196,
                  0.1441,
                  0.024,
                  0.0015,
                  0.0]
    evaluator = Evaluator()
    t = 0
    start = time.clock()
    while counter < test_count:
        success = False
        deck = Deck()
        for i in range(5):
            cards.append(deck.pop())
        #cards = cards_straight_flush()
        evaluator.set_cards(cards)
        score = evaluator.evaluate()

        success_count[score] += 1
        cards = []
        counter += 1
        #break
    t = time.clock() - start
    print test_count, "evaluations in", '%5.3f' % t, "seconds (", '%.3f' % (
        float(t * 1000) / test_count), "ms/evaluation)"
    print "Score".ljust(10), "Count".ljust(10), "Succ%".rjust(10), "Delta%".rjust(10)
    for i in range(10):
        perc = (float(success_count[i]) * 100 / test_count)
        error = math.fabs(perc - percentage[i])
        print str(i).ljust(10), str(success_count[i]).ljust(10), (str(perc) + "%").rjust(10), (
            str('%.2f' % error) + "%").rjust(10)
